/*
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
 *
 * Copyright 2010 huliqing, huliqing.cn@gmail.com
 *
 * This file is part of QFaces.
 * QFaces is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * QFaces is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with QFaces.  If not, see <http://www.gnu.org/licenses/>.
 *
 * 这个文件是QFaces的一部分。
 * 您可以单独使用或分发这个文件，但请不要移除这个头部声明信息.
 * QFaces是一个自由软件，您可以自由分发、修改其中的源代码或者重新发布它，
 * 新的任何修改后的重新发布版必须同样在遵守LGPL3或更后续的版本协议下发布.
 * 关于LGPL协议的细则请参考COPYING、COPYING.LESSER文件，
 * 您可以在QFaces的相关目录中获得LGPL协议的副本，
 * 如果没有找到，请连接到 http://www.gnu.org/licenses/ 查看并获取副本。
 *
 * - Author: Huliqing
 * - Contact: huliqing.cn@gmail.com
 * - License: GNU Lesser General Public License (LGPL)
 * - Blog and source code availability: http://www.huliqing.name/
 */

package name.huliqing.qfaces.render;

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.logging.Logger;
import javax.el.MethodExpression;
import javax.faces.component.UIColumn;
import javax.faces.component.UIComponent;
import javax.faces.component.UIData;
import javax.faces.component.UIForm;
import javax.faces.component.html.HtmlGraphicImage;
import javax.faces.component.html.HtmlOutputLink;
import javax.faces.component.html.HtmlOutputText;
import javax.faces.component.html.HtmlPanelGroup;
import javax.faces.context.FacesContext;
import javax.faces.context.ResponseWriter;
import javax.faces.render.Renderer;
import javax.servlet.http.HttpServletRequest;
import name.huliqing.qfaces.QFaces;
import name.huliqing.qfaces.component.Scroller;
import name.huliqing.qfaces.model.PageModel;
import name.huliqing.qfaces.model.PageParam;

public class ScrollerRenderer extends Renderer {

    private final static Logger logger = Logger.getLogger(ScrollerRenderer.class.getName());
    // URL 参数
    private String CONS_RECORD_START;
    private String CONS_RECORD_SORT_BY;
    private String CONS_RECORD_SORT_ASC;
    // 排序连接ID前缀，完整格式可能如下  "xxx:sortLink1", "xxx:sortLink2", "xxx:sortLink3"
    // 各列linkId都不同
    private final static String PREFIX_SORT_LINK = "sortLink";
    // 排序图片ID前缀
    private final static String PREFIX_SORT_IMAGE = "sortImage";
    // 目标组件
    private Scroller s;
    // 二维数组,pPages[n][2]用于显示的页数数组 <<- p1 p2 p3 ... ->>
    private int[][] pPages;
    private String srcPageFirst;
    private String srcPageLast;
    private String srcPagePrevious;
    private String srcPageNext;

    @Override
    public void decode(FacesContext fc, UIComponent ui) {
        super.decode(fc, ui);
    }

    @Override
    public void encodeBegin(FacesContext fc, UIComponent ui)
            throws IOException {
        super.encodeBegin(fc, ui);
        s = (Scroller) ui;

        // ---- 组件绑定的JS资源
        QFaces.getResLoader2().loadJavaScript(ui, "Scroller.js");

        // ---- 检查参数
        if (s.getFor() == null) {
            throw new NullPointerException("Target not found(attribute 'for')");
        }

        if (s.getDisplay() == null) {
            s.setDisplay("bottom");
        }
        if (s.getDisplayPage() == null) {
            s.setDisplayPage(10);
        }
        if (s.getDisplayCount() == null) {
            s.setDisplayCount(Boolean.TRUE);
        }
        if (s.getDisplayJump() == null) {
            s.setDisplayJump(Boolean.TRUE);
        }
        if (s.getDisplayJumpBySelect() == null) {
            s.setDisplayJumpBySelect(Boolean.FALSE);
        }
        if (s.getPageSize() == null) {
            s.setPageSize(20);
        }
        if (s.getVisible() == null) {
            s.setVisible(true);
        }
        if (s.getStyle() == null) {
            s.setStyle("");
        }
        if (s.getStyleClassMouseOn() == null) {
            s.setStyleClassMouseOn("_qf_01_mouseOn");
        }
        if (s.getStyleClassMouseOut() == null) {
            s.setStyleClassMouseOut("_qf_01_mouseOut");
        }
        if (s.getStyleClassCurrent() == null) {
            s.setStyleClassCurrent("_qf_01_current");
        }
        if (s.getStyleClassImageMouseOn() == null) {
            s.setStyleClassImageMouseOn("_qf_01_imageMouseOn");
        }
        if (s.getStyleClassImageMouseOut() == null) {
            s.setStyleClassImageMouseOut("_qf_01_imageMouseOut");
        }
        if (s.getFace() == null) {
            s.setFace("1");
        }
        if (s.getStrRecordTotal() == null) {
            s.setStrRecordTotal("总数:");
        }
        if (s.getStrPageTotal() == null) {
            s.setStrPageTotal("页数:");
        }
        if (s.getStrJump() == null) {
            s.setStrJump("转到");
        }
        if (s.getSrcAsc() == null) {
            s.setSrcAsc(QFaces.getResLoader2().makeImageSrc("01-asc.png"));
        }
        if (s.getSrcDesc() == null) {
            s.setSrcDesc(QFaces.getResLoader2().makeImageSrc("01-desc.png"));
        }
        if (s.getMethod() == null) {
            s.setMethod("post");
        }

        // ---- URL参数，这里必须以clientId区分不同组件，因为页面可能同时存在
        // 多个scroller,如果不进行区分，则会导致一个翻页操作同时控制了几个scroller
        CONS_RECORD_START = s.getClientId(fc) + ":start";
        CONS_RECORD_SORT_BY = s.getClientId(fc) + ":sort";
        CONS_RECORD_SORT_ASC = s.getClientId(fc) + ":asc";

        // 优先从srcPage...中获取图标路径，再从face中组件默认路径
        if (s.getSrcPageFirst() != null) {
            this.srcPageFirst = s.getSrcPageFirst();
        } else {
            this.srcPageFirst = QFaces.getResLoader2().makeImageSrc("01-" + s.getFace() + "-page-first.gif");
        }
        if (s.getSrcPageLast() != null) {
            this.srcPageLast = s.getSrcPageLast();
        } else {
            this.srcPageLast = QFaces.getResLoader2().makeImageSrc("01-" + s.getFace() + "-page-last.gif");
        }
        if (s.getSrcPageNext() != null) {
            this.srcPageNext = s.getSrcPageNext();
        } else {
            this.srcPageNext = QFaces.getResLoader2().makeImageSrc("01-" + s.getFace() + "-page-next.gif");
        }
        if (s.getSrcPagePrevious() != null) {
            this.srcPagePrevious = s.getSrcPagePrevious();
        } else {
            this.srcPagePrevious = QFaces.getResLoader2().makeImageSrc("01-" + s.getFace() + "-page-previous.gif");
        }

        // ---- 开始逻辑

        Object targetTemp = s.findComponent(s.getFor());
        if (!(targetTemp instanceof javax.faces.component.UIData)) {
            logger.severe("Target UIData not found!");
        }
        UIData target = (UIData) targetTemp;

        // ---- 重置Scroller属性

        // 计算起始记录,首先偿试从URL中获取recordStart,如果不存在，
        // 则偿试从Scroller中恢复，如果不存在，则使用默认值0,并同步Scroller
        Integer recordStart = calculateRecordStart(target);
        if (recordStart != null) {
            s.setRecordStart(recordStart);
        }
        if (s.getRecordStart() == null) {
            s.setRecordStart(0);
        }

        // 计算排序信息,优先从URL中获取，否则从Scroller中恢复
        String sortField = QFaces.getParam(CONS_RECORD_SORT_BY);
        if (sortField != null) {
            s.setSortField(sortField);
        }

        Boolean asc = QFaces.convertToBoolean(QFaces.getParam(CONS_RECORD_SORT_ASC));
        if (asc != null) {
            s.setAsc(asc);
        }

        // 计算分页参数信息
        PageParam pageParam = new PageParam(s.getRecordStart(), s.getPageSize(), s.getSortField(), s.getAsc());
        pageParam.setScroller(s);
        pageParam.setUiData(target);

        // 获取数据信息
        PageModel<?> pm = null;
        MethodExpression listener = s.getListener();
        if (listener == null) {
            logger.severe("\"listener\" method not found, "
                    + "check the attribute of \"listener\" and the binding method of your backing bean.");
        }
        pm = (PageModel<Object>) listener.invoke(fc.getELContext(), new Object[]{pageParam});

        // 如果找不到数据则创建一个空的.
        if (pm == null) {
            pm = new PageModel<Object>();
            logger.warning("No data with the PageModel(" + s.getClass().getName() + ")");
        }

        // 计算总页数
        s.setPageTotal((int) Math.ceil((double) pm.getTotal() / s.getPageSize()));
        s.setRecordTotal(pm.getTotal());

        // 计算当前页
        s.setPageCurrent(s.getRecordStart() / s.getPageSize());// 刚好整除

        // 计算用于在页面显示的页码
        this.pPages = calculatePages(s.getPageTotal(), s.getDisplayPage(), s.getRecordStart(), s.getPageSize());

        // 绑定数据及引用

        target.setValue(pm.getPageData());
        if (s.getVar() != null) {
            target.setVar(s.getVar());
        }

        // ------------------------------------- 关于排序

        // Before Render children
        // Pre encodeChildren: 创建表头,创建排序字段
        if (s.getSortFields() != null) {
            // 用于排序的各个字串
            String[] sortFields = s.getSortFields().split(",");
            // 找出UIData 下的所有UIColumn
            List<UIComponent> children = target.getChildren();
            List<UIColumn> columns = new ArrayList<UIColumn>();
            for (UIComponent child : children) {
                if (child instanceof UIColumn) {
                    columns.add((UIColumn) child);
                }
            }
            if (sortFields != null && sortFields.length > 0 && columns.size() > 0) {
                UIForm uiForm = this.getForm(s);
                String formId = (uiForm != null ? uiForm.getClientId(fc) : "");
                for (int i = 0; i < sortFields.length; i++) {
                    if (i >= columns.size()) {
                        break;
                    }
                    String sf = sortFields[i].trim();
                    // 用于排序的字段名，如果为null或空，则超过
                    if (sf.equals("")) {
                        continue;
                    }
                    // UIData下的column下必须有name为header的facet才能添加排序
                    UIColumn column = columns.get(i);
                    UIComponent header = column.getFacet("header");
                    if (header == null) {
                        continue;
                    }
                    // 开始处理排序信息
                    // URL中的排序参数
                    StringBuilder URLPrefix = new StringBuilder(this.getURL(fc));
                    URLPrefix.append("?").append(CONS_RECORD_SORT_BY).append("=").append(sf);
                    URLPrefix.append("&").append(CONS_RECORD_START).append("=0");	// 排序时将起始行置为0;
                    URLPrefix.append("&").append(CONS_RECORD_SORT_ASC).append("=");
                    // 排序图标,这个图标根据asc为true/false而所指向的src会不同
                    HtmlPanelGroup group = null;
                    HtmlOutputLink sortLink = null;
                    HtmlGraphicImage image = null;
                    // 首次需要生成排序信息，之后则只更新排序属性就OK
                    if (header instanceof HtmlOutputText) {
                        String title = (String) ((HtmlOutputText) header).getValue();
                        group = (HtmlPanelGroup) fc.getApplication().createComponent(HtmlPanelGroup.COMPONENT_TYPE);
                        sortLink = (HtmlOutputLink) fc.getApplication().createComponent(HtmlOutputLink.COMPONENT_TYPE);
                        HtmlOutputText text = (HtmlOutputText) fc.getApplication().createComponent(HtmlOutputText.COMPONENT_TYPE);
                        if (sf.equalsIgnoreCase(s.getSortField())) {
                            image = (HtmlGraphicImage) fc.getApplication().createComponent(HtmlGraphicImage.COMPONENT_TYPE);
                            image.setStyle("vertical-align:middle;");
                            image.setId(PREFIX_SORT_IMAGE + i); // ID标识
                            image.setUrl(s.getAsc() ? s.getSrcAsc() : s.getSrcDesc());
                            URLPrefix.append(s.getAsc() ? "false" : "true");
                        } else {
                            URLPrefix.append("true");
                        }
                        text.setValue(title);
                        sortLink.setId(PREFIX_SORT_LINK + i); // ID标识
                        sortLink.setValue("javascript:void(0)");
                        sortLink.getChildren().add(text);
                        sortLink.setOnclick("Q.F.UIScroller.sortLink('" + s.getMethod() + "', '" + URLPrefix + "', '" + formId + "')");
                        group.getChildren().add(sortLink);
                        if (image != null) {
                            group.getChildren().add(image);
                        }
                        column.getFacets().put("header", group);

                        // 更新排序信息，这一步只是更新排序图标及连接，因为排序组件已经存在。
                    } else if (header instanceof HtmlPanelGroup) {
                        group = (HtmlPanelGroup) header;
                        List<UIComponent> gChildren = group.getChildren();
                        if (gChildren != null && !gChildren.isEmpty()) {
                            // 从children中查找sortLink及sortImage
                            for (UIComponent child : gChildren) {
                                if (child instanceof HtmlOutputLink && child.getId().startsWith(PREFIX_SORT_LINK)) {
                                    sortLink = (HtmlOutputLink) child;
                                }
                                if (child instanceof HtmlGraphicImage && child.getId().startsWith(PREFIX_SORT_IMAGE)) {
                                    image = (HtmlGraphicImage) child;
                                }
                            }
                        }
                        if (sf.equalsIgnoreCase(s.getSortField())) {
                            if (image == null) {
                                image = (HtmlGraphicImage) fc.getApplication().createComponent(HtmlGraphicImage.COMPONENT_TYPE);
                                image.setStyle("vertical-align:middle;");
                                image.setId(PREFIX_SORT_IMAGE + i); // ID标识
                                group.getChildren().add(image);
                            }
                            image.setUrl(s.getAsc() ? s.getSrcAsc() : s.getSrcDesc());
                            URLPrefix.append(s.getAsc() ? "false" : "true");
                        } else {
                            // 需要移除非当前排序的图标
                            if (image != null) {
                                group.getChildren().remove(image);
                            }
                            URLPrefix.append("true");
                        }
                        if (sortLink != null) {
                            sortLink.setOnclick("Q.F.UIScroller.sortLink('" + s.getMethod() + "', '" + URLPrefix + "', '" + formId + "')");
                        }
                    }
                }
            }
        }

        // ---- 开始渲染Scroller组件

        ResponseWriter rw = fc.getResponseWriter();
        rw.startElement("div", s);
        rw.writeAttribute("style", "border:0;", null);
        if (s.getVisible()) {
            if ("top".equalsIgnoreCase(s.getDisplay()) || "both".equalsIgnoreCase(s.getDisplay())) {
                encodeScroller(fc, "top");
            }
        }
    }

    @Override
    public void encodeChildren(FacesContext fc, UIComponent ui)
            throws IOException {
        super.encodeChildren(fc, ui);
    }

    @Override
    public void encodeEnd(FacesContext fc, UIComponent ui)
            throws IOException {
        ResponseWriter rw = fc.getResponseWriter();
        if (s.getVisible()) {
            if ("bottom".equalsIgnoreCase(s.getDisplay()) || "both".equalsIgnoreCase(s.getDisplay())) {
                encodeScroller(fc, "bottom");
            }
        }
        rw.endElement("div");
    }

    // position参数只是为标识是top scroller还是bottom scroller,因为组件可以同时渲染在两个地方(top,bottom)
    private void encodeScroller(FacesContext fc, String position) throws IOException {
        // 如果存在排序信息，则需要添加排序参数到URL请求参数中
        String URLPrefix = null;
        if (s.getSortField() != null && !s.getSortField().equals("")) {
            URLPrefix = getURL(fc)
                    + "?" + CONS_RECORD_SORT_BY + "=" + s.getSortField()
                    + "&" + CONS_RECORD_SORT_ASC + "=" + s.getAsc()
                    + "&" + CONS_RECORD_START + "="; // URL前缀;
        } else {
            URLPrefix = getURL(fc)
                    + "?" + CONS_RECORD_START + "="; // URL前缀
        }

        // 加入form主要是为了让js能够提交该form.如果通过简单的OutputLink方式跳转页面，
        // 则存在一些不足，会让页面中form里的其它field无法提交，这会让其它field所绑定的BackingBean
        // 的数据重置，而不能恢复原来的值，这对页面查询（搜索）分页时保存搜索条件时非常有用
        UIForm uiForm = getForm(s);
        String formId = (uiForm != null ? uiForm.getClientId(fc) : "");

        // 开始渲染scroller
        ResponseWriter rw = fc.getResponseWriter();
        rw.startElement("table", s);
        rw.writeAttribute("border", "0", null);
        rw.writeAttribute("cellspacing", "0", null);
        rw.writeAttribute("cellpadding", "1", null);
        rw.writeAttribute("style", s.getStyle(), null);
        rw.startElement("tr", s);

        // encode 箭头 <<
        encodeArrowPrevious(fc, URLPrefix, formId);

        // encode 翻页
        // page[0]为页码，page[1]为当前页的起始记录, url为跳转的目的地址（通过form的post方式跳转）
        // currentPage 表示是否为当前页，在当前页下，渲染输出会不一样，以提醒用户当前所在的页码
        rw.startElement("td", s);
        rw.startElement("table", s);
        rw.writeAttribute("border", "0", null);
        rw.startElement("tr", s);
        for (int[] page : this.pPages) {
            String url = URLPrefix + page[1];
            String label = String.valueOf(page[0] + 1);
            Boolean currentPage = (s.getPageCurrent() == page[0]);
            encodeTD(rw, formId, label, url, currentPage);
        }
        rw.endElement("tr");
        rw.endElement("table");
        rw.endElement("td");

        // encode 箭头 >>
        encodeArrowNext(fc, URLPrefix, formId);

        // encode 总记录
        if (s.getDisplayCount()) {
            encodeInfo(rw);
        }

        // encode JUMP To
        if (s.getDisplayJump()) {
            encodeJump(fc, URLPrefix, formId, position);
        }

        // End encode
        rw.endElement("tr");
        rw.endElement("table");
    }

    private void encodeTD(ResponseWriter rw, String formId, String label, String url, boolean currentPage) throws IOException {
        String jsEvent = "Q.F.UIScroller.pageLinkEvent(\"" + s.getMethod() + "\", \"" + url + "\", \"" + formId + "\");";
        rw.startElement("td", s);
        rw.writeAttribute("style", "text-align:center;", null);
        if (currentPage) { // 如果是当前页，则加粗
            rw.writeAttribute("class", s.getStyleClassCurrent(), null);
            rw.writeText(label, null);
        } else {
            rw.writeAttribute("class", s.getStyleClassMouseOut(), null);
            rw.writeAttribute("onmouseover", "this.className='" + s.getStyleClassMouseOn() + "'", null);
            rw.writeAttribute("onmouseout", "this.className='" + s.getStyleClassMouseOut() + "'", null);
            rw.writeAttribute("onclick", jsEvent, null);
            rw.writeText(label, null);
        }
        rw.endElement("td");
    }

    private String getURL(FacesContext fc) {
        HttpServletRequest request = (HttpServletRequest) fc.getExternalContext().getRequest();
        String viewId = request.getServletPath();
        String contextPath = fc.getExternalContext().getRequestContextPath();
        return (contextPath + viewId);
    }

    private void encodeJump(FacesContext fc,
            String URLPrefix, String formId, String position) throws IOException {
        ResponseWriter rw = fc.getResponseWriter();
        // 渲染select进行跳转，
        if (s.getDisplayJumpBySelect()) {
            StringBuilder js = new StringBuilder("Q.F.UIScroller.jumpTo(")
                    .append(" '" + s.getMethod() + "'")
                    .append(",'" + URLPrefix + "'")
                    .append(",this.value")
                    .append(",'" + formId + "'")
                    .append(")");

            rw.startElement("td", s);
            rw.startElement("select", s);
            rw.writeAttribute("size", "1", null);
            rw.writeAttribute("onchange", js, null);
            for (int i = 0; i < s.getPageTotal(); i++) {
                rw.startElement("option", s);
                rw.writeAttribute("value", i * s.getPageSize(), null);
                if (i == s.getPageCurrent()) {
                    rw.writeAttribute("selected", "true", null);
                }
                rw.writeText(i + 1, null);
                rw.endElement("option");
            }
            rw.endElement("select");
            rw.endElement("td");
        }

        // ---- 渲染input框进行跳转

        if (s.getDisplayJump()) {
            // 用于跳转的input field的ID, 必须加上position以区别,因为组件可能同时渲染在top和bottom
            // 如果没有区别，则会渲染出两个相同ID的input field;
            String idOfInputJump = s.getClientId(fc) + ":idOfInputJump:" + position;

            StringBuilder js_jumpByEnter = new StringBuilder("Q.F.UIScroller.jumpByEnter(")
                    .append(" \"" + s.getMethod() + "\"")
                    .append(",\"" + URLPrefix + "\"")
                    .append(",\"" + idOfInputJump + "\"")
                    .append(",\"" + formId + "\"")
                    .append(",\"" + s.getPageSize() + "\"")
                    .append(",\"" + s.getPageTotal() + "\"")
                    .append(",event")
                    .append(")");

            StringBuilder js_jumpByClick = new StringBuilder("Q.F.UIScroller.jumpByClick(")
                    .append(" '" + s.getMethod() + "'")
                    .append(",'" + URLPrefix + "'")
                    .append(",'" + idOfInputJump + "'") // idOfInputJump
                    .append(",'" + formId + "'")
                    .append(",'" + s.getPageSize() + "'")
                    .append(",'" + s.getPageTotal() + "'")
                    .append(")");

            rw.startElement("td", s);
            rw.startElement("input", s);
            rw.writeAttribute("id", idOfInputJump, null);
            rw.writeAttribute("name", idOfInputJump, null);
            rw.writeAttribute("type", "text", null);
            rw.writeAttribute("style", "width:40px;", null);
            rw.writeAttribute("value", (s.getPageCurrent() + 1), null);
            rw.writeAttribute("onkeydown", js_jumpByEnter, null);
            rw.endElement("input");
            rw.startElement("input", s);
            rw.writeAttribute("type", "button", null);
            rw.writeAttribute("value", s.getStrJump(), null);
            rw.writeAttribute("onclick", js_jumpByClick, null);
            rw.endElement("input");
            rw.endElement("td");
        }
    }

    private void encodeInfo(ResponseWriter rw) throws IOException {
        rw.startElement("td", s);
        rw.writeText(s.getStrRecordTotal() + s.getRecordTotal(), null);
        rw.endElement("td");
        rw.startElement("td", s);
        rw.writeText(s.getStrPageTotal() + s.getPageTotal(), null);
        rw.endElement("td");
    }

    private void encodeArrowPrevious(FacesContext fc, String URLPrefix, String formId) throws IOException {
        Integer pageCurrent = s.getPageCurrent();

        // 是否需要渲染事件响应，因为达到第一页时，是不需要再响应“向前翻页”动作的
        Boolean previousEvent = pageCurrent > 0;

        // 计算首页及前一页起始记录
        Integer record_first = 0;
        Integer record_previous = (pageCurrent - 1) * s.getPageSize();

        // 事件响应
        StringBuilder jsEvent_first = new StringBuilder("Q.F.UIScroller.imageLink(")
                .append(" \"" + s.getMethod() + "\"")
                .append(",\"" + URLPrefix + "\"")
                .append(",\"" + record_first + "\"")
                .append(",\"" + formId + "\"")
                .append(")");

        StringBuilder jsEvent_previous = new StringBuilder("Q.F.UIScroller.imageLink(")
                .append(" \"" + s.getMethod() + "\"")
                .append(",\"" + URLPrefix + "\"")
                .append(",\"" + record_previous + "\"")
                .append(",\"" + formId + "\"")
                .append(")");

        // 翻到第一页
        ResponseWriter rw = fc.getResponseWriter();
        rw.startElement("td", s);
        this.encodeImage(fc, this.srcPageFirst, (previousEvent ? jsEvent_first.toString() : null));
        rw.endElement("td");

        // 翻到前一页
        rw.startElement("td", s);
        this.encodeImage(fc, this.srcPagePrevious, (previousEvent ? jsEvent_previous.toString() : null));
        rw.endElement("td");
    }

    private void encodeImage(FacesContext fc, String src, String jsEvent) throws IOException {
        ResponseWriter rw = fc.getResponseWriter();
        rw.startElement("img", s);
        rw.writeAttribute("border", "0", null);
        rw.writeAttribute("src", src, null);
        rw.writeAttribute("alt", "", null);
        rw.writeAttribute("class", s.getStyleClassImageMouseOut(), null);
        if (jsEvent != null) {
            rw.writeAttribute("onclick", jsEvent, null);
            rw.writeAttribute("onmouseover", "this.className='" + s.getStyleClassImageMouseOn() + "'", null);
            rw.writeAttribute("onmouseout", "this.className='" + s.getStyleClassImageMouseOut() + "'", null);
        }
        rw.endElement("img");
    }

    // 参考：encodeArrowPrevious
    private void encodeArrowNext(FacesContext fc, String URLPrefix, String formId) throws IOException {
        // 因为pageCurrent是从0算起，所以减1
        Integer pageCurrent = s.getPageCurrent();
        Integer pageTotal = s.getPageTotal();
        Boolean nextEvent = pageCurrent < pageTotal - 1;
        Integer record_next = (pageCurrent + 1) * s.getPageSize();
        Integer record_last = (pageTotal - 1) * s.getPageSize();

        StringBuilder jsEvent_next = new StringBuilder("Q.F.UIScroller.imageLink(")
                .append(" \"" + s.getMethod() + "\"")
                .append(",\"" + URLPrefix + "\"")
                .append(",\"" + record_next + "\"")
                .append(",\"" + formId + "\"")
                .append(")");

        StringBuilder jsEvent_last = new StringBuilder("Q.F.UIScroller.imageLink(")
                .append(" \"" + s.getMethod() + "\"")
                .append(",\"" + URLPrefix + "\"")
                .append(",\"" + record_last + "\"")
                .append(",\"" + formId + "\"")
                .append(")");

        ResponseWriter rw = fc.getResponseWriter();
        rw.startElement("td", s);
        this.encodeImage(fc, this.srcPageNext, (nextEvent ? jsEvent_next.toString() : null));
        rw.endElement("td");

        rw.startElement("td", s);
        this.encodeImage(fc, this.srcPageLast, (nextEvent ? jsEvent_last.toString() : null));
        rw.endElement("td");
    }

    // ---------- calculate
    /**
     * 计算页面数组
     * @param pageTotal
     * @param pageDisplay
     * @param recordStart
     * @param pageSize
     * @return
     */
    private int[][] calculatePages(Integer pageTotal, Integer pageDisplay,
            Integer recordStart, Integer pageSize) {
        int startPage = 0;
        int totalToDisplay = 0;
        // 起始记录
        int recordStartDisplay = 0;
        // 取中点,始终将当前页显示在中间
        int center = (int) Math.ceil(pageDisplay / 2);
        // 如果所有页面数小于指定要显示的数目,则从0记录开始显示
        if (pageTotal <= pageDisplay) {
            recordStartDisplay = 0;
            totalToDisplay = pageTotal;
        } else {
            // 否则尽量将记录向前移动一半，以让当前页显示在中间位置
            recordStartDisplay = recordStart - center * pageSize;
            if (recordStartDisplay < 0) {
                recordStartDisplay = 0;
            }
            // 计算出起始记录页
            startPage = recordStartDisplay / pageSize;
            int theEnd = startPage + pageDisplay;
            // 如果后面的页数不足以显示pageDisplay数目的记录
            if (theEnd > pageTotal) {
                int offset = theEnd - pageTotal;
                // 则再次前移
                recordStartDisplay -= offset * pageSize;
                if (recordStartDisplay < 0) {
                    recordStartDisplay = 0;
                }
            }
        }
        // 重新计算起始页
        startPage = recordStartDisplay / pageSize;

        // 计算出剩下的页数
        int leavePage = pageTotal - startPage;
        totalToDisplay = pageDisplay <= leavePage ? pageDisplay : leavePage;
        int[][] _pages = new int[totalToDisplay][2];
        for (int i = 0; i < totalToDisplay; i++) {
            int[] page = new int[2];
            // 页码
            page[0] = startPage + i;
            // 页记录
            page[1] = recordStartDisplay + i * pageSize;
            _pages[i] = page;
        }
        return _pages;
    }

    // 计算起始记录
    private Integer calculateRecordStart(UIData target) {
        Integer result = null;
        // 优先从parameter中获取,注意检查recordStart,这是从客户端来的，可能存在各种可能的值
        // 如非正数，非数字，超过最多记录数等。
        Integer recordStart = QFaces.convertToInteger(QFaces.getParam(CONS_RECORD_START));
        if (recordStart != null) {
            result = recordStart;
        } else {
            // 注意,如果用户在backingBean中绑定了uiData,并且可能在后台
            // 手动设置了first（起始记录行）,那么这里需要使用该值.
            // Note:对于分页查询来说，在bean中指定first属性一般都应该指定为0.
            // 即从首记录开始显示。如：pageSize=10, 在后台查询数据后，默认first应该仍是0;
            // 如果手动指定了first(通过绑定的uiData),如指定了first=2, 那么界面上将只显示
            // 8条记录，因为pageSize=10,first=2,不要惊讶后面的数据为什么没有显示，因为这是
            // 分页查询，只查了一页数据（10条记录）.
            // 只有在反复查询的情况下，可能first属性被缓存起来的情况下，才应该手动重置first=0.
            // 否则有时候你真的会看不到数据（有数据而没有显示），就如data只够1页，而first已经
            // 指定到了第2页或更多.
            Integer first = target.getFirst();	// 如果没有手动设置,那么这里target.getFirst()应该始终返回0
            if (first != 0) {	// 这可以避免每次删除记录后都跳回到第1页
                result = first;
            }
        }
        return result;
    }

    private UIForm getForm(UIComponent ui) {
        if (ui == null) {
            return null;
        } else {
            if (ui instanceof UIForm) {
                return (UIForm) ui;
            } else {
                return getForm(ui.getParent());
            }
        }
    }

    @Override
    public boolean getRendersChildren() {
        return true; 
    }


}

